"""BLE Connection Management Service.

Provides GATT characteristics for connection management,
status monitoring, and session handling.
"""

import asyncio
import json
import logging
import struct
import time
from typing import Optional, Callable, Dict, Any, List
from enum import Enum

from ..connection_manager import (
    ConnectionManager,
    ConnectionState,
    ConnectionPriority,
    Session
)

logger = logging.getLogger(__name__)


# Service and Characteristic UUIDs
CONNECTION_SERVICE_UUID = "0000FF50-0000-1000-8000-00805F9B34FB"
CONNECTION_INFO_UUID = "0000FF51-0000-1000-8000-00805F9B34FB"    # Read
CONNECTION_CONTROL_UUID = "0000FF52-0000-1000-8000-00805F9B34FB" # Write
CONNECTION_STATUS_UUID = "0000FF53-0000-1000-8000-00805F9B34FB"  # Read/Notify
SESSION_MGMT_UUID = "0000FF54-0000-1000-8000-00805F9B34FB"       # Read/Write


class ConnectionCommand(Enum):
    """Connection control commands."""
    CONNECT = 0x01
    DISCONNECT = 0x02
    HEARTBEAT = 0x03
    STATUS_REQUEST = 0x04
    LIST_CONNECTIONS = 0x05
    SET_PRIORITY = 0x06
    TERMINATE_SESSION = 0x07
    CLEANUP_IDLE = 0x08


class ConnectionService:
    """BLE Connection Management Service implementation."""
    
    def __init__(self, connection_manager: ConnectionManager):
        """Initialize connection service.
        
        Args:
            connection_manager: Connection manager instance
        """
        self.connection_manager = connection_manager
        
        # Notification callback
        self.notify_callback: Optional[Callable[[str, bytes], None]] = None
        
        # Service registration data
        self.service_data = self._create_service_definition()
        
        # Status notification interval
        self.status_notify_interval = 30  # 30 seconds
        self.status_task: Optional[asyncio.Task] = None
        
        # Setup connection manager callbacks
        self.connection_manager.on_connection_established = self._on_connection_established
        self.connection_manager.on_connection_lost = self._on_connection_lost
        
        logger.info("Connection service initialized")
    
    def _create_service_definition(self) -> Dict[str, Any]:
        """Create GATT service definition.
        
        Returns:
            Service definition dictionary
        """
        return {
            "uuid": CONNECTION_SERVICE_UUID,
            "primary": True,
            "characteristics": [
                {
                    "uuid": CONNECTION_INFO_UUID,
                    "flags": ["read"],
                    "value": bytearray(256),  # Connection info data
                    "descriptors": []
                },
                {
                    "uuid": CONNECTION_CONTROL_UUID,
                    "flags": ["write"],
                    "value": bytearray(64),   # Control commands
                    "descriptors": []
                },
                {
                    "uuid": CONNECTION_STATUS_UUID,
                    "flags": ["read", "notify"],
                    "value": bytearray(128),  # Status information
                    "descriptors": [
                        {
                            "uuid": "00002902-0000-1000-8000-00805F9B34FB",  # CCC
                            "flags": ["read", "write"],
                            "value": bytearray([0x00, 0x00])
                        }
                    ]
                },
                {
                    "uuid": SESSION_MGMT_UUID,
                    "flags": ["read", "write"],
                    "value": bytearray(512),  # Session management
                    "descriptors": []
                }
            ]
        }
    
    async def start(self):
        """Start connection service."""
        # Start periodic status notifications
        self.status_task = asyncio.create_task(self._status_notification_loop())
        logger.info("Connection service started")
    
    async def stop(self):
        """Stop connection service."""
        if self.status_task:
            self.status_task.cancel()
            try:
                await self.status_task
            except asyncio.CancelledError:
                pass
        logger.info("Connection service stopped")
    
    # GATT Characteristic Handlers
    
    async def handle_read_connection_info(self) -> bytes:
        """Handle read from CONNECTION_INFO characteristic.
        
        Returns:
            Connection information data
        """
        try:
            # Get all active connections
            connections = self.connection_manager.get_active_connections()
            
            connection_list = []
            for conn in connections[:5]:  # Limit to first 5 connections
                connection_list.append({
                    "connection_id": conn.connection_id[:8],  # Shortened ID
                    "device_id": conn.device_id,
                    "state": conn.state.value,
                    "priority": conn.priority.value,
                    "connected_time": int(conn.connected_time or 0),
                    "last_activity": int(conn.last_activity_time),
                    "bytes_sent": conn.bytes_sent,
                    "bytes_received": conn.bytes_received,
                    "errors": conn.errors_count
                })
            
            info_data = {
                "total_connections": len(connections),
                "connections": connection_list,
                "timestamp": int(time.time())
            }
            
            # Convert to JSON and encode
            json_str = json.dumps(info_data)
            return json_str.encode('utf-8')[:256]  # Limit to 256 bytes
            
        except Exception as e:
            logger.error(f"Error reading connection info: {e}")
            return b'{"error": "Failed to read connection info"}'
    
    async def handle_write_connection_control(self, value: bytes) -> bool:
        """Handle write to CONNECTION_CONTROL characteristic.
        
        Args:
            value: Control command bytes
            
        Returns:
            True if handled successfully
        """
        try:
            if not value:
                return False
            
            command = ConnectionCommand(value[0])
            params = value[1:] if len(value) > 1 else b''
            
            if command == ConnectionCommand.CONNECT:
                return await self._handle_connect_command(params)
            
            elif command == ConnectionCommand.DISCONNECT:
                return await self._handle_disconnect_command(params)
            
            elif command == ConnectionCommand.HEARTBEAT:
                return await self._handle_heartbeat_command(params)
            
            elif command == ConnectionCommand.STATUS_REQUEST:
                await self._send_status_notification()
                return True
            
            elif command == ConnectionCommand.LIST_CONNECTIONS:
                await self._send_connection_list()
                return True
            
            elif command == ConnectionCommand.SET_PRIORITY:
                return await self._handle_set_priority_command(params)
            
            elif command == ConnectionCommand.TERMINATE_SESSION:
                return await self._handle_terminate_session_command(params)
            
            elif command == ConnectionCommand.CLEANUP_IDLE:
                return await self._handle_cleanup_idle_command()
            
            else:
                logger.warning(f"Unknown connection command: {command}")
                return False
                
        except Exception as e:
            logger.error(f"Error handling connection control: {e}")
            return False
    
    async def handle_read_connection_status(self) -> bytes:
        """Handle read from CONNECTION_STATUS characteristic.
        
        Returns:
            Connection status data
        """
        try:
            stats = self.connection_manager.get_statistics()
            
            # Pack status into binary format for efficiency
            status_data = struct.pack(
                '>BBBBIFF',  # Big endian: 4 bytes + 2 ints + 2 floats
                stats['pool']['active_connections'],      # Active connections (1 byte)
                stats['pool']['total_connections'],       # Total connections (1 byte)  
                self.connection_manager.max_connections,  # Max connections (1 byte)
                min(100, int(stats['pool']['utilization_percent'])),  # Utilization % (1 byte)
                int(time.time()),                         # Current timestamp (4 bytes)
                stats['metrics']['data_sent'],            # Data sent (4 bytes)
                stats['metrics']['average_response_time'] # Response time (4 bytes)
            )
            
            return status_data
            
        except Exception as e:
            logger.error(f"Error reading connection status: {e}")
            return b'\x00' * 20  # Return zeros on error
    
    async def handle_read_session_management(self) -> bytes:
        """Handle read from SESSION_MGMT characteristic.
        
        Returns:
            Session management data
        """
        try:
            session_stats = self.connection_manager.session_manager.get_statistics()
            
            session_data = {
                "total_sessions": session_stats["total_sessions"],
                "active_sessions": session_stats["active_sessions"],
                "authenticated_sessions": session_stats["authenticated_sessions"],
                "expired_sessions": session_stats["expired_sessions"],
                "timestamp": int(time.time())
            }
            
            return json.dumps(session_data).encode('utf-8')[:512]
            
        except Exception as e:
            logger.error(f"Error reading session management: {e}")
            return b'{"error": "Failed to read session data"}'
    
    async def handle_write_session_management(self, value: bytes) -> bool:
        """Handle write to SESSION_MGMT characteristic.
        
        Args:
            value: Session management command
            
        Returns:
            True if handled successfully
        """
        try:
            data = json.loads(value.decode('utf-8'))
            command = data.get('command')
            
            if command == 'cleanup_expired':
                cleaned = await self.connection_manager.session_manager.cleanup_expired_sessions()
                logger.info(f"Cleaned up {cleaned} expired sessions")
                return True
            
            elif command == 'get_session':
                session_id = data.get('session_id')
                if session_id:
                    session = self.connection_manager.session_manager.get_session(session_id)
                    if session:
                        session_info = {
                            "session_id": session.session_id,
                            "connection_id": session.connection_id,
                            "created_time": session.created_time,
                            "last_access_time": session.last_access_time,
                            "expires_at": session.expires_at,
                            "is_authenticated": session.is_authenticated
                        }
                        await self._send_notification(SESSION_MGMT_UUID, 
                                                    json.dumps(session_info).encode('utf-8')[:512])
                        return True
            
            elif command == 'update_session':
                session_id = data.get('session_id')
                session_data = data.get('data', {})
                if session_id:
                    success = self.connection_manager.session_manager.update_session(session_id, session_data)
                    return success
            
            return False
            
        except Exception as e:
            logger.error(f"Error handling session management write: {e}")
            return False
    
    # Command Handlers
    
    async def _handle_connect_command(self, params: bytes) -> bool:
        """Handle connect command.
        
        Args:
            params: Command parameters
            
        Returns:
            True if handled successfully
        """
        try:
            # Parse parameters (device_id and priority)
            if len(params) < 2:
                return False
            
            device_id = params[:-1].decode('utf-8').rstrip('\x00')
            priority_val = params[-1]
            priority = ConnectionPriority(priority_val)
            
            # Simulate accepting a connection (normally would come from BLE stack)
            client_address = f"00:11:22:33:44:{priority_val:02X}"
            
            connection = await self.connection_manager.accept_connection(
                device_id, client_address, priority
            )
            
            if connection:
                logger.info(f"Connection established via command: {connection.connection_id}")
                return True
            
            return False
            
        except Exception as e:
            logger.error(f"Connect command error: {e}")
            return False
    
    async def _handle_disconnect_command(self, params: bytes) -> bool:
        """Handle disconnect command.
        
        Args:
            params: Command parameters (connection_id or device_id)
            
        Returns:
            True if handled successfully
        """
        try:
            if not params:
                return False
            
            identifier = params.decode('utf-8').rstrip('\x00')
            
            # Try as connection ID first
            connection = self.connection_manager.get_connection(identifier)
            
            if not connection:
                # Try as device ID
                connection = self.connection_manager.get_connection_by_device(identifier)
            
            if connection:
                await self.connection_manager.disconnect_connection(
                    connection.connection_id, "Manual disconnect via control"
                )
                return True
            
            logger.warning(f"Connection not found for disconnect: {identifier}")
            return False
            
        except Exception as e:
            logger.error(f"Disconnect command error: {e}")
            return False
    
    async def _handle_heartbeat_command(self, params: bytes) -> bool:
        """Handle heartbeat command.
        
        Args:
            params: Command parameters (optional connection_id)
            
        Returns:
            True if handled successfully
        """
        try:
            if params:
                # Heartbeat specific connection
                connection_id = params.decode('utf-8').rstrip('\x00')
                connection = self.connection_manager.get_connection(connection_id)
                
                if connection:
                    success = await self.connection_manager.monitor.send_heartbeat(connection)
                    return success
                
                return False
            else:
                # Heartbeat all connections
                connections = self.connection_manager.get_active_connections()
                heartbeat_tasks = []
                
                for conn in connections:
                    heartbeat_tasks.append(
                        self.connection_manager.monitor.send_heartbeat(conn)
                    )
                
                if heartbeat_tasks:
                    results = await asyncio.gather(*heartbeat_tasks, return_exceptions=True)
                    success_count = sum(1 for r in results if r is True)
                    logger.info(f"Sent heartbeats to {success_count}/{len(heartbeat_tasks)} connections")
                
                return True
                
        except Exception as e:
            logger.error(f"Heartbeat command error: {e}")
            return False
    
    async def _handle_set_priority_command(self, params: bytes) -> bool:
        """Handle set priority command.
        
        Args:
            params: Command parameters (connection_id + priority)
            
        Returns:
            True if handled successfully
        """
        try:
            if len(params) < 2:
                return False
            
            connection_id = params[:-1].decode('utf-8').rstrip('\x00')
            priority_val = params[-1]
            priority = ConnectionPriority(priority_val)
            
            connection = self.connection_manager.get_connection(connection_id)
            if connection:
                connection.priority = priority
                logger.info(f"Set priority for {connection_id} to {priority.name}")
                return True
            
            return False
            
        except Exception as e:
            logger.error(f"Set priority command error: {e}")
            return False
    
    async def _handle_terminate_session_command(self, params: bytes) -> bool:
        """Handle terminate session command.
        
        Args:
            params: Command parameters (session_id)
            
        Returns:
            True if handled successfully
        """
        try:
            if not params:
                return False
            
            session_id = params.decode('utf-8').rstrip('\x00')
            success = self.connection_manager.session_manager.remove_session(session_id)
            
            if success:
                logger.info(f"Terminated session: {session_id}")
            
            return success
            
        except Exception as e:
            logger.error(f"Terminate session command error: {e}")
            return False
    
    async def _handle_cleanup_idle_command(self) -> bool:
        """Handle cleanup idle connections command.
        
        Returns:
            True if handled successfully
        """
        try:
            idle_connections = self.connection_manager.pool.get_idle_connections()
            
            for connection in idle_connections:
                await self.connection_manager.disconnect_connection(
                    connection.connection_id, "Idle cleanup"
                )
            
            logger.info(f"Cleaned up {len(idle_connections)} idle connections")
            return True
            
        except Exception as e:
            logger.error(f"Cleanup idle command error: {e}")
            return False
    
    # Event Handlers
    
    def _on_connection_established(self, connection):
        """Handle connection established event.
        
        Args:
            connection: Established connection
        """
        asyncio.create_task(self._send_connection_event("established", connection))
    
    def _on_connection_lost(self, connection):
        """Handle connection lost event.
        
        Args:
            connection: Lost connection
        """
        asyncio.create_task(self._send_connection_event("lost", connection))
    
    # Notification Methods
    
    async def _send_connection_event(self, event_type: str, connection):
        """Send connection event notification.
        
        Args:
            event_type: Type of event
            connection: Connection object
        """
        try:
            event_data = {
                "event": event_type,
                "connection_id": connection.connection_id[:8],
                "device_id": connection.device_id,
                "timestamp": int(time.time())
            }
            
            data = json.dumps(event_data).encode('utf-8')[:128]
            await self._send_notification(CONNECTION_STATUS_UUID, data)
            
        except Exception as e:
            logger.error(f"Error sending connection event: {e}")
    
    async def _send_status_notification(self):
        """Send status notification."""
        try:
            status_data = await self.handle_read_connection_status()
            await self._send_notification(CONNECTION_STATUS_UUID, status_data)
            
        except Exception as e:
            logger.error(f"Error sending status notification: {e}")
    
    async def _send_connection_list(self):
        """Send connection list notification."""
        try:
            connections = self.connection_manager.get_active_connections()
            
            connection_list = []
            for conn in connections[:10]:  # Limit to 10 connections
                connection_list.append({
                    "id": conn.connection_id[:8],
                    "device": conn.device_id,
                    "state": conn.state.name,
                    "priority": conn.priority.name
                })
            
            list_data = {
                "connections": connection_list,
                "total": len(connections),
                "timestamp": int(time.time())
            }
            
            data = json.dumps(list_data).encode('utf-8')[:256]
            await self._send_notification(CONNECTION_INFO_UUID, data)
            
        except Exception as e:
            logger.error(f"Error sending connection list: {e}")
    
    async def _send_notification(self, characteristic_uuid: str, data: bytes):
        """Send notification for a characteristic.
        
        Args:
            characteristic_uuid: UUID of characteristic
            data: Notification data
        """
        if self.notify_callback:
            self.notify_callback(characteristic_uuid, data)
    
    # Background Tasks
    
    async def _status_notification_loop(self):
        """Background status notification loop."""
        while True:
            try:
                await asyncio.sleep(self.status_notify_interval)
                await self._send_status_notification()
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Status notification loop error: {e}")
    
    # Service Interface
    
    def set_notify_callback(self, callback: Callable[[str, bytes], None]):
        """Set notification callback.
        
        Args:
            callback: Callback function for notifications (uuid, data)
        """
        self.notify_callback = callback
    
    def get_service_definition(self) -> Dict[str, Any]:
        """Get GATT service definition.
        
        Returns:
            Service definition for registration
        """
        return self.service_data
    
    def get_connection_statistics(self) -> Dict[str, Any]:
        """Get connection service statistics.
        
        Returns:
            Service statistics
        """
        stats = self.connection_manager.get_statistics()
        
        return {
            "service": {
                "is_running": self.status_task is not None and not self.status_task.done(),
                "notification_interval": self.status_notify_interval
            },
            "manager": stats
        }
    
    async def cleanup(self):
        """Clean up service resources."""
        await self.stop()
        logger.info("Connection service cleanup completed")


# Example usage
if __name__ == "__main__":
    import asyncio
    from ..connection_manager import ConnectionManager
    
    async def test_connection_service():
        """Test connection service functionality."""
        # Create connection manager and service
        manager = ConnectionManager()
        service = ConnectionService(manager)
        
        # Mock notification callback
        notifications = []
        def notify_callback(uuid, data):
            notifications.append((uuid, data))
        
        service.set_notify_callback(notify_callback)
        
        # Start services
        await manager.start()
        await service.start()
        
        try:
            # Test connection establishment
            connection = await manager.accept_connection(
                "test_device", "AA:BB:CC:DD:EE:FF"
            )
            
            if connection:
                print(f"Connection established: {connection.connection_id}")
                
                # Test reading connection info
                info_data = await service.handle_read_connection_info()
                print(f"Connection info: {info_data.decode('utf-8')}")
                
                # Test status read
                status_data = await service.handle_read_connection_status()
                print(f"Status data length: {len(status_data)} bytes")
                
                # Test control command (heartbeat)
                control_cmd = bytes([ConnectionCommand.HEARTBEAT.value])
                success = await service.handle_write_connection_control(control_cmd)
                print(f"Heartbeat command: {'success' if success else 'failed'}")
                
                # Wait for notifications
                await asyncio.sleep(2)
                print(f"Received {len(notifications)} notifications")
                
                # Test disconnection
                disconnect_cmd = bytes([ConnectionCommand.DISCONNECT.value]) + connection.connection_id.encode()
                success = await service.handle_write_connection_control(disconnect_cmd)
                print(f"Disconnect command: {'success' if success else 'failed'}")
            
        finally:
            # Clean up
            await service.cleanup()
            await manager.cleanup()
    
    # Run test
    asyncio.run(test_connection_service())